<!-- $1 -->
<template>
  <div>
    <FileUpload
        @uploader="onUploader"
        @select="onUploadSelect"
        @remove="onUploadRemove"
        :accept="props.accept"
        :customUpload="true"
        :multiple="true"
        :auto="false"
        :maxFileSize="1073741824">
      <template #header="{chooseCallback, uploadCallback, clearCallback}">
        <div class="flex flex-wrap justify-content-between align-items-center flex-1 gap-2">
          <div class="flex gap-2">
            <Button @click="chooseCallback()"
                    icon="pi pi-upload"
                    class="p-button-rounded"></Button>
            <Button @click="uploadCallback()"
                    :loading="uploadStatus.loading"
                    :disabled="(!uploadStatus.open && !uploadStatus.loading) || renderFiles.length <= 0"
                    icon="pi pi-cloud-upload"
                    class="p-button-rounded p-button-success"></Button>
            <Button @click="clearCallback()"
                    icon="pi pi-times"
                    class="p-button-rounded p-button-danger"></Button>
          </div>
          <InputText class="flex align-items-end flex-wrap justify-content-end" placeholder="上传码"
                     v-model="uploadCode"
                     v-if="showInput"
                     v-tooltip.top="'不好意思捏，目前只能通过联系管理员来获取上传码哦(~●\'◡\'●)~'"/>
        </div>
      </template>
      <template #content="{files}">
        <div v-if="renderFiles && renderFiles.length > 0">
          <div class="m-file-list">
            <div class="grid" v-for="(file, index) of renderFiles" :key="file.filename">
              <!-- 文件 svg -->
              <div class="col-1 m-file-svg" v-html="file.svg"></div>
              <!-- 文件信息 -->
              <div class="col-10">
                <div class="grid pl-4">
                  <div class="col-12">
                    {{ file.filename }}
                  </div>
                  <div class="col-12">
                    {{ file.size }}
                    <Badge :value="file.text" :severity="file.color"></Badge>
                  </div>
                </div>
              </div>
              <!-- 上传进度 -->
              <div class="col-12">
                <ProgressBar class="relative" :value="file.progress" :show-value="false"/>
              </div>
              <!-- 文件操作按钮 -->
              <div class="col-12">
                      <span class="uploader-opera-icon"
                            @click="uploaderOperaHandler({mode: -1, index: index, files: files, file: file })"
                            v-html="CommonSvgJson.upload.delete"></span>
                <span class="uploader-opera-icon"
                      @click="uploaderOperaHandler({mode: 0, index: index, files: files })"
                      :class="{'m-disabled': !file.open && file.status === -1}"
                      v-html="CommonSvgJson.upload.start"></span>
                <span class="uploader-opera-icon"
                      @click="uploaderOperaHandler({mode: 1, index: index, file: file.file })"
                      :class="{'m-disabled': file.status !== 1}"
                      v-html="CommonSvgJson.upload.paused"></span>
              </div>
              <div class="col-12">
              </div>
            </div>
          </div>
        </div>
      </template>
      <template #empty>
        <div class="flex align-items-center justify-content-center flex-column" v-if="renderFiles.length === 0">
          <i class="pi pi-upload border-2 border-circle p-5 text-8xl text-400 border-400"/>
          <p class="mt-4 mb-0">拖动或者点击按钮上传文件</p>
        </div>
      </template>
    </FileUpload>
  </div>
</template>

<script lang="ts" setup>
/** JS */
import {ref, Ref, watch, defineProps} from 'vue';
import {FileService} from '@/utils/ApiRequest';
import {FileUploadRender} from '@/interface/file/entity/FileEntittyInterface';
import toast from '@/utils/Toast';
import {initUploader, mathMd5, UploaderConfig} from '@/utils/Upload';
import Cache from '@/utils/Cache';
/** 组件 */
import FileUpload from 'primevue/fileupload';
import ProgressBar from 'primevue/progressbar';
import Badge from 'primevue/badge';
/** JSON */
import FileExtSvgJson from '@/assets/json/FileExtSvgJson.json';
import CommonSvgJson from '@/assets/json/CommonSvgJson.json';
import FileExtCommonDesc from '@/assets/json/FileExtCommonDesc.json';

const cache = new Cache(), fileService = new FileService();
const props = defineProps({
  url: {
    type: String,
    required: true
  },
  path: {
    type: String,
    required: true
  },
  showInput: {
    type: Boolean
  },
  accept: {
    type: String
  }
})
/** 后缀数组 */
let exts: string[] = [];
for (let key in FileExtSvgJson) {
  exts.push(key);
}

/** 文件上传 */
const renderFiles: Ref<FileUploadRender[]> = ref([]),
    uploadStatus = {
      // 是否允许上传
      open: false,
      // 上传按钮加载动画
      loading: false,
      // 上传异常锁
      error: false
    },
    uploadCode = ref(''),
    errorFiles: Ref<any []> = ref([]);

// 上传配置参数
const uploadConfig: UploaderConfig = {
  server: props.url,
  chunkSize: 1024 << 10,
  threads: 5,
  uploadBeforeSend: async (obj, data, headers) => {
    let defaultData = {
          // 上传目录
          path: props.path,
          // 上传码
          uploadCode: uploadCode.value
        },
        // 获取md5，obj.file[WuFile].source[WuFile#File].source[File]
        md5 = obj.file.source.source.md5;
    headers.Authorization = cache.get('token');
    for (let key in defaultData) {
      data[key] = defaultData[key];
    }
    if (md5) {
      data.md5 = md5;
    }
  },
  uploadAcceptFunction: async (object, ret) => {
    if (ret.code !== 0) {
      toast.error('上传失败', ret.msg, 3000);
      uploadStatus.error = true;
      // 上传失败后，文件会从 WebUploader 中移除。
      // console.log('上传失败的文件 = ', object)
      // WebUploader.addFiles 无效
      webUploader.addFiles(object.file.source.source)
      // 加入错误文件队列，用于重新上传
      errorFiles.value.push(object);
    }
  },
  uploadProgressFunction: async (file, progress) => {
    for (let valueElement of renderFiles.value) {
      let valueProgress = valueElement.progress ? valueElement.progress : 0;
      if (file.source.source.md5 === valueElement.md5) {
        if (uploadStatus.error) {
          // 如果错误锁开启，那么直接归 0
          valueElement.progress = 0;
          return;
        }
        if (progress >= valueProgress / 100) {
          valueElement.progress = progress * 100;
        }
      }
    }
  },
  uploadSuccessFunction: async (file, response) => {
    let f = getFileByWU(file);
    if (f) {
      f.progress = 100;
      f.text = '上传完毕';
      f.color = 'danger';
      f.status = 2;
      f.open = false;
    }
  }
}
// 监听错误列表，如果有新增数据则直接回调至 webUploader 当中
watch(() => errorFiles.value, (n) => {
  if (n && n.length > 0) {
    for (let ef of n) {
      // console.log('获取错误文件列表重新塞入 webupload 当中 --> ', ef.file.source.source.name);
      webUploader.addFiles(ef.file.source.source);
    }
    errorFiles.value = [];
    // console.log('清空错误列表', errorFiles.value);
  }
}, {deep: true});
// 初始化 WebUploader
const webUploader = initUploader(uploadConfig);
// 计算文件 str 大小
const formatSize = (bytes) => {
      if (bytes === 0) {
        return '0 B';
      }
      let k = 1000,
          dm = 3,
          sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
          i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    },
    // 根据 file 获取 WebUploader#WUFile
    getWUFile = (file) => {
      const files = webUploader.getFiles();
      for (let tmp of files) {
        let md5 = tmp.source.source.md5;
        if (file.md5 === md5) {
          return tmp;
        }
      }
    },
    // 根据 WUFile 获取 File
    getFileByWU = (wuFile) => {
      const files = renderFiles.value;
      for (let tmp of files) {
        let md5 = wuFile.source.source.md5;
        if (tmp.md5 === md5) {
          return tmp;
        }
      }
    },
    // 获取文件后缀
    getExt = (str: string) => {
      let index = str.lastIndexOf('.') + 1;
      let ext = str.substring(index);
      if (FileExtCommonDesc.zips.indexOf(ext) !== -1) {
        return 'zip';
      } else if (FileExtCommonDesc.world.indexOf(ext) !== -1) {
        return 'world';
      } else if (FileExtCommonDesc.excel.indexOf(ext) !== -1) {
        return 'excel';
      } else if (FileExtCommonDesc.process.indexOf(ext) !== -1) {
        return 'process';
      } else if (exts.indexOf(ext) === -1) {
        return 'unknown';
      }
      return ext;
    };

//上传操作
const onUploader = (ev) => {
      // 此处对文件列表进行全部上传
      for (let f of renderFiles.value) {
        webUploader.upload(getWUFile(f.file));
        f.status = 1;
      }
    },
    // 上传文件选择后的操作
    onUploadSelect = (event) => {
      // events#files 是记录当前上传的文件数组
      let files: any[] = event.files;
      for (let tmpFile of files) {
        let currentFile: any = tmpFile;
        // 跳过重复文件
        let arr = renderFiles.value.map(f => {
          return {
            name: f.filename,
            size: f.size
          }
        }).filter(f => f.name === currentFile.name && f.size === formatSize(currentFile.size))
        if (arr.length > 0) continue;
        // 获取文件后缀确认 svg 选择
        let ext = getExt(currentFile.name);
        // 选择文件时计算 md5 值，并加入 webUploader 以及构建渲染文件的数组
        mathMd5(currentFile,
            async (md5, file) => {
              // 记录 MD5 入文件
              currentFile.md5 = md5;
              // 提前判断获取进度是否有问题，如果有问题提前禁止上传
              let res = await fileService.getProcess(md5);
              if (res !== undefined) {
                // 计算 md5 完成后改变状态
                renderFiles.value[file.index].color = 'success';
                renderFiles.value[file.index].text = '等待上传';
                renderFiles.value[file.index].open = true;
                renderFiles.value[file.index].status = 0;
                renderFiles.value[file.index].md5 = md5;
                // 保存进度
                currentFile.currentChunk = res;
                webUploader.addFiles(currentFile);
                // 上传按钮退出 loading
                uploadStatus.loading = false;
              } else {
                toast.error('出问题啦', '可能上传存在某个未发现的问题哦~联系下管理员吧!', 1500);
              }
            },
            (file) => {
              // 动态渲染当前指定文件
              let render: FileUploadRender = {
                svg: FileExtSvgJson[ext] ? FileExtSvgJson[ext] : FileExtSvgJson.unknown,
                filename: currentFile.name,
                size: formatSize(currentFile.size),
                text: '计算 MD5 中',
                color: 'info',
                open: false,
                status: -1,
                file: currentFile
              }
              // 记录下标并加入文件对象属性当中
              file.index = renderFiles.value.length;
              renderFiles.value.push(render);
              // 上传按钮进入 loading
              uploadStatus.loading = true;
            });
      }
    },
    // 文件删除操作
    onUploadRemove = (event) => {
      // console.log('remove ', event);
    }
// 动态渲染文件列表的数据，用于判断是否开放上传
watch(renderFiles, (n) => {
  if (n && n.length > 0) {
    let flag = true;
    for (let f of n) {
      if (!f.open && f.status === 0) {
        // 文件数组中如果等待上传状态且为关闭时禁止上传
        flag = false;
        break;
      }
    }
    uploadStatus.open = flag;
  }
}, {deep: true});
// 按钮操作, -1 删除 | 0 上传 | 1 停止
type UploaderOperaConfig = {
  mode: -1 | 0 | 1
  index: number
  files?: any[]
  file?: any
}
// 小按钮的操作
const uploaderOperaHandler = (config: UploaderOperaConfig) => {
  let mode = config.mode, files = config.files, index = config.index, file = config.file;
  switch (mode) {
    case -1:
      if (files) {
        webUploader.removeFile(getWUFile(file), true);
        files.splice(index, 1);
        renderFiles.value.splice(index, 1);
      } else {
        console.error('获取文件缓存失败');
      }
      break;
    case 0:
      if (file) {
        webUploader.upload(getWUFile(file));
        renderFiles.value[index].status = 1;
      } else {
        console.error('获取文件缓存失败');
      }
      break;
    case 1:
      if (file) {
        webUploader.stop(getWUFile(file));
        renderFiles.value[index].status = 0;
      } else {
        console.error('获取文件缓存失败');
      }
      break;
  }
}
</script>

<style scoped>

</style>
